package replica;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.concurrent.TimeUnit;

import org.omg.CORBA.ORB;

import administrator.administratorService;
import administrator.administratorServiceHelper;
import player.playerService;
import player.playerServiceHelper;
import common.CommonTools;
import common.Marshall;
import common.UDPTools_1;

public class Replica {
	
	static int bufferSize = 1000;
		
	// this function may be used at the replica level to receive UDP message from  to the replica leader and reply
	// it can also be used at the leader to receive the message from the front end and reply
	
	public  void receiveReplyUDP(int UDPPort,String receiver,String role,   String senderRole) {		
		DatagramSocket serverSocket = null;
		InetAddress receiverAddress;
		byte[] bufferOut = new byte[bufferSize];
		byte[] bufferIn = new byte[bufferSize];
	
		boolean continueListening = true;
		
		try {
			serverSocket = new DatagramSocket(UDPPort);

			while (continueListening) {
				DatagramPacket request = new DatagramPacket(bufferIn, bufferIn.length);
				
				serverSocket.receive(request);
				String  requestMessage = new String(request.getData()).trim();

				CommonTools.writeLog(
						role,
						role,
						"New UDP Request received :" + requestMessage
								, "replica");
							 
				
				//reply phase
				//getLocalUserStatus();
				//String stringOut = processRequest(role, requestMessage);
				String stringOut = "true";
				
				// we have to process the request here and fill the stringOut
				bufferOut = stringOut.getBytes();
				DatagramPacket reply = new DatagramPacket(bufferOut,
						bufferOut.length, request.getAddress(),
						request.getPort());
				serverSocket.send(reply);
				
				CommonTools
						.writeLog(
								role,
								role,
								"A UDP response was sent back with the result:"
								+ stringOut, "replica");
			}

		} catch (SocketException e) {
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			if (serverSocket != null)
				serverSocket.close();
		}
	}
	
	private  String processRequest(String role, String  requestMessage) {
		
		String result = null;
		//steps
		// execute the request locally 
		// send the request to the replicas and get the result if i am the leader	
		//compare the results if i am the leader
		//return  the result back
		
		
			
		// call the local execution.
		//String result1 = executeLocalCall(requestMessage,role);
		String result1 = "true";
		
		// I have to manage time out using a thread	
		// this message should be  multicasted in the future
		if(!role.equals("leader")){
			result = result1;
			
		} else {
		String result2 = UDPTools_1.sendReceiveUDP( Integer.parseInt(CommonTools.getproperty("replica.properties","UDPPort_" + "replica_2")),
														CommonTools.getproperty("replica.properties", "serverName_" + "replica_2"), 
													   "replica_2",   "Leader", requestMessage);
		
		String result3 = UDPTools_1.sendReceiveUDP( Integer.parseInt(CommonTools.getproperty("replica.properties","UDPPort_" + "replica_3")),
				CommonTools.getproperty("replica.properties", "serverName_" + "replica_3"), 
			   "replica_3",   "Leader", requestMessage);
		
		result = GetBestResultNotifyRM(result1,result2,result3);	
		}
		return result;
		
	}
	
	private  String GetBestResultNotifyRM(String result1, String result2,String result3){
		
		// this is a basic comparison we may un-marshall the string to make a sophisticated one
		// this based  on the assumption that only one result should be wrong
		String result = null;
		String result1_status = null;
		String result2_status = null;
		String result3_status = null;
		
		if (result1.equals(result2)){
			result = result1;
			result1_status  = "OK";
			result2_status  = "OK";
			if(result1.equals(result3)){
				result3_status  = "OK";
			}else{
				result3_status  = "NOK";
			}
				
		}else if (result1.equals(result3)){
			result = result1;
			result1_status  = "OK";
			result2_status  = "NOK";
			result3_status  = "OK";
			
		}else{
			result = result2;			
			result2_status  = "OK";
			if(result2.equals(result3)){
				result3_status  = "OK";
				result1_status  = "NOK";
			}else{
				result3_status  = "NOK";
				result1_status  = "OK";
			}
		}
		
		//String notifactionresult = sendNotification(result1_status,result2_status,result3_status);
		
		CommonTools.writeLog(
				"leader",
				"leader",
				"New notification send to the RM notifactionresult:" //+ notifactionresult
						, "replica");
		
		return result;
	}	

	private String executeLocalCall(String UDPRequest,String server ){
				
		String serviceName		= 	Marshall.getValue("Service", UDPRequest);
		String operationName	=	Marshall.getValue("Method", UDPRequest);
		String geolocation  	= null;
		
		ORB orbEU = ORB.init(new String[1], null);
		ORB orbAS = ORB.init(new String[1], null);
		ORB orbNA = ORB.init(new String[1], null);
		
		//player
		if(serviceName.equals("player")){
			
			
			String playerIOR_EU = CommonTools.getIOR("player", server+"_EU","replica");
			String playerIOR_AS = CommonTools.getIOR("player", server+"_AS","replica");
			String playerIOR_NA = CommonTools.getIOR("player", server+"_NA","replica");

			org.omg.CORBA.Object playerServiceObjectEU = orbEU.string_to_object(playerIOR_EU);
			org.omg.CORBA.Object playerServiceObjectAS = orbAS.string_to_object(playerIOR_AS);
			org.omg.CORBA.Object playerServiceObjectNA = orbNA.string_to_object(playerIOR_NA);
			
			playerService playerServiceEU = playerServiceHelper.narrow(playerServiceObjectEU);
			playerService playerServiceAS = playerServiceHelper.narrow(playerServiceObjectAS);
			playerService playerServiceNA = playerServiceHelper.narrow(playerServiceObjectNA);
			
			PlayerClient playerClient	= null;
			String playerIPAddress	=	Marshall.getValue("PlayerIpAddress", UDPRequest);
			
			//get geolocation			
			
			if(CommonTools.isLegalIpAddress(playerIPAddress,"EU")){
				playerClient= new PlayerClient(playerServiceEU);
				geolocation = "EU";
			} else if(CommonTools.isLegalIpAddress(playerIPAddress,"AS")){
				playerClient= new PlayerClient(playerServiceAS);
				geolocation= "AS";
			} else {
				playerClient= new PlayerClient(playerServiceNA);
				geolocation ="NA";
			}
			
			
			
			//create			
			if(operationName.equals("createAccount")){
				
				return createPlayer(UDPRequest, geolocation, playerClient );
			}
			//sign in
			else if(operationName.equals("signIn")){
				return signInPlayer(UDPRequest, geolocation, playerClient );
				
			}
			//sign out
			else if(operationName.equals("signOut")){
				return signOutPlayer(UDPRequest, geolocation, playerClient );
			}
			//transfer
			else{
				return transferPlayer(UDPRequest, geolocation, playerClient );
			}
		}
		//admin
		else{
			String adminIOR_EU = CommonTools.getIOR("admin", server+"EU","replica");
			String adminIOR_AS = CommonTools.getIOR("admin", server+"AS","replica");
			String adminIOR_NA = CommonTools.getIOR("admin", server+"NA","replica");
	
			org.omg.CORBA.Object administratorServiceEUObject = orbEU.string_to_object(adminIOR_EU);
			org.omg.CORBA.Object administratorServiceASObject = orbAS.string_to_object(adminIOR_AS);
			org.omg.CORBA.Object administratorServiceNAObject = orbNA.string_to_object(adminIOR_NA);
			
			administratorService administratorServiceEU = administratorServiceHelper.narrow(administratorServiceEUObject);
			administratorService administratorServiceAS = administratorServiceHelper.narrow(administratorServiceASObject);
			administratorService administratorServiceNA = administratorServiceHelper.narrow(administratorServiceNAObject);
			
			AdminClient adminClient = null;
			
			String playerIPAddress	=	Marshall.getValue("AdminIpAddress", UDPRequest);
			if(CommonTools.isLegalIpAddress(playerIPAddress,"EU")){
				adminClient= new AdminClient(administratorServiceEU);
				geolocation = "EU";
			} else if(CommonTools.isLegalIpAddress(playerIPAddress,"AS")){
				adminClient= new AdminClient(administratorServiceAS);
				geolocation= "AS";
			} else {
				adminClient= new AdminClient(administratorServiceNA);
				geolocation ="NA";
			}
			
			//player status
			if(operationName.equals("playerStatus")){
				return getPlayerStatus(UDPRequest, geolocation, adminClient );
			}
			//suspend account
			else if(operationName.equals("suspendAccount")){
				return suspendAccount(UDPRequest, geolocation, adminClient );
			}
			else{
				return halt();
			}
			
		}
	}
	
	private String createPlayer(String UDPRequest, String geolocation,PlayerClient playerClient ) {
		String result = null;
		
		result = playerClient.createPlayer( Marshall.getValue("FirstName", UDPRequest),
											Marshall.getValue("LastName", UDPRequest) , 
											Integer.parseInt(Marshall.getValue("Age", UDPRequest)), 
											Marshall.getValue("UserName", UDPRequest) , 
											Marshall.getValue("Password", UDPRequest) , 
											Marshall.getValue("PlayerIpAddress", UDPRequest) );
		
		return result;
	}
	private String signInPlayer(String UDPRequest, String geolocation,PlayerClient playerClient ) {
		String result = null;
		
		result = playerClient.signIn(	Marshall.getValue("UserName", UDPRequest) , 
										Marshall.getValue("Password", UDPRequest) , 
										Marshall.getValue("PlayerIpAddress", UDPRequest) );
		
		return result;
	}
	private String signOutPlayer(String UDPRequest, String geolocation,PlayerClient playerClient ) {
		String result = null;
		
		result = playerClient.signOut( Marshall.getValue("UserName", UDPRequest) , 
									   Marshall.getValue("PlayerIpAddress", UDPRequest) );
		
		return result;
	}
	private String transferPlayer(String UDPRequest, String geolocation,PlayerClient playerClient ) {
		String result = null;
		
		result = playerClient.transferAccount(Marshall.getValue("UserName", UDPRequest) , 
											Marshall.getValue("Password", UDPRequest) , 
											Marshall.getValue("PlayerIpAddress", UDPRequest),
											Marshall.getValue("NewPlayerIpAddress", UDPRequest));
		
		return result;
	}
	
	private String getPlayerStatus(String UDPRequest, String geolocation, AdminClient adminClient ) {
		String result = null;
		
		result = adminClient.getPlayerStatus(Marshall.getValue("AdminName", UDPRequest) , 
											Marshall.getValue("AdminPassword", UDPRequest) , 
											Marshall.getValue("AdminIpAddress", UDPRequest));
		
		return result;
	}
	private String suspendAccount(String UDPRequest, String geolocation, AdminClient adminClient ) {
		String result = null;
		
		result = adminClient.suspendAccount(Marshall.getValue("AdminName", UDPRequest) , 
											Marshall.getValue("AdminPassword", UDPRequest) , 
											Marshall.getValue("AdminIpAddress", UDPRequest),
											Marshall.getValue("UserNameToSuspend", UDPRequest));
		
		return result;
	}
	
	private String sendNotification(String result1_status,String result2_status,String result3_status) {
		String message = null;
		String result = null;
		
		message = Marshall.putTag(null, "Result1", result1_status);
		message = Marshall.putTag(message, "Result2", result2_status);
		message = Marshall.putTag(message, "Result3", result3_status);
		
		result = UDPTools_1.sendReceiveUDP(	Integer.parseInt(CommonTools.getproperty("replica.properties","UDPPort_" + "replica_manager")),
											CommonTools.getproperty("replica.properties", "serverName_" + "replica_manager"), 
										  	"replica_manager", 
										  	"leader", 
										  	message);
		return result;
	}
	
	public String halt(){
		return null;
	}
	
}
